
#ifndef LOD_BOX_H
#define LOD_BOX_H

#include <vector>
#include <cmath>
#include <assert.h>
#include <stdlib.h>
#include <sys/time.h>

#include "plane.h"
#include "ball.h"

#include "../base/point3.h"

//todo
#include <iostream>
using std::cout;
using std::endl;
using std::flush;


// http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/
// http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonmesh/

class box
{
    public:
    box(float Minx, float Maxx,
        float Miny, float Maxy,
        float Minz, float Maxz)
     : minx(Minx), maxx(Maxx),
       miny(Miny), maxy(Maxy),
       minz(Minz), maxz(Maxz)
    {
        assert(minx<maxx);
        assert(miny<maxy);
        assert(minz<maxz);
    }

    box()
     : minx(-50), maxx(50),
       miny(-50), maxy(50),
       minz(-50), maxz(50) {}

    void tick(float t)
    {
        // move each ball
        unsigned end=b.size();
        for(unsigned i=0; i<end; i++)
        {
            ball& bl = b[i];
            bl.move(t);

        // check if the ball still lies inside the inner space
        // if it has gone outside,
        // find the reflection vector from the nearest plane
        // and leave the ball at the previous position
        // we don't care about minor distortions of balls' movement 
        // balls don't interact with each other
        // an assumption: planes' normals point inside the space
        //                  and planes' normals have the length 1 (they're unit vectors)
        // balls outside the unpassable borders are deleted

            

            if(bl.out) {
                if(unpassable(bl)) { // it unpassable(bl)==true but bl.del==false,
                                    // it means one big jump has been done; 
                                    // we give the ball a chance to return to the cage
                    bl.del=true;
                    continue;
                }
                
                plane& pl = p[bl.plane];
                float dist = pl.A*bl.pos.x + pl.B*bl.pos.y + pl.C*bl.pos.z + pl.D;
                if(dist<=0) // still out
                    continue;
                else { // okay, now the ball's inside the inner space
                    bl.out=false;
                    continue;
                }
                // we always continue to assure that the ball will 
                // go back to the inner space
            }

            // checking if the ball is inside the inner space
            unsigned pend=p.size();
            if(pend>0) 
            {
                float dist;
                float mindist;
                unsigned minpi;
                unsigned pi;

                // the nearest plane is the one that the ball made off the most 
                mindist=1;
                for(pi=0; pi<pend; pi++) {
                    plane& pl = p[pi];
                    dist = pl.A*bl.pos.x + pl.B*bl.pos.y + pl.C*bl.pos.z + pl.D;
                    if(dist<mindist) {
                        mindist = dist;
                        minpi = pi;
                    }
                }

                // now mindist = the distance from the closest plain (p[minpi]), 
                // that the ball has made off, if any

                if(mindist<=0) {
                    //cout<<"m "<<mindist<<" "<<minpi<<endl<<flush;
                    // compute the reflaction vector
                    // vpar = (n dot V)n
                    // R = V - 2vpar
                    plane& pl = p[minpi];
                    float dot = pl.A*bl.vel.x + pl.B*bl.vel.y + pl.C*bl.vel.z;
                    bl.vel.x = bl.vel.x - 2*dot*pl.A;
                    bl.vel.y = bl.vel.y - 2*dot*pl.B;
                    bl.vel.z = bl.vel.z - 2*dot*pl.C;
                    bl.restore_pos();
                    bl.out=true;
                    bl.plane=minpi;
                }
            }
        }

        unsigned newi=0;
        for(unsigned i=0; i<end; i++) {
            if(b[i].del==false) {
                b[newi]=b[i];
                newi++;
            }
        }

        if(newi<end) { // some balls have been deleted
            b.resize(newi);
            cout<<end-newi<<" balls have been deleted (had gone too far)"<<endl<<flush;
        }

        //cout<<"tick"<<endl<<flush;
    }

    void add_plane(float A,float B,float C,float D)
    {
        p.push_back(plane(A,B,C,D));
        unsigned i=p.size();
        p[i-1].normalize();
    }

    void add_ball(float pos_x, float pos_y, float pos_z, 
                  float vel_x, float vel_y, float vel_z,
                  float radius)
    {
        ball bl(pos_x, pos_y, pos_z, vel_x, vel_y, vel_z, radius);
        check(bl);
        b.push_back(bl);
    }

    void add_random_ball(float left, float right,
                        float bottom, float top,
                        float back, float front,
                        float vmin, float vmax,
                        float minrad, float maxrad)
    {
        struct timeval stamp;
        long l;
        float pos_x, pos_y, pos_z;
        float vel_x, vel_y, vel_z;
        float radius;
        double width;

        gettimeofday(&stamp,NULL);
        srandom((unsigned int)stamp.tv_usec);
        
        if((width=right-left)!=0) {
            l = random();
            pos_x = width * (l/(double)RAND_MAX) + left;
        }
        else
            pos_x=left;

        if((width=top-bottom)!=0) {
            l = random();
            pos_y = width * (l/(double)RAND_MAX) + bottom;
        }
        else
            pos_y=bottom;

        if((width=front-back)!=0) {
            l = random();
            pos_z = width * (l/(double)RAND_MAX) + back;
        }
        else
            pos_z=back;

        if((width=vmax-vmin)!=0) {
            l = random();
            vel_x = width * (l/(double)RAND_MAX) + vmin;
            l = random();
            vel_y = width * (l/(double)RAND_MAX) + vmin;
            l = random();
            vel_z = width * (l/(double)RAND_MAX) + vmin;
        }
        else {
            vel_x=vmin;
            vel_y=vmin;
            vel_z=vmin;
        }

        if((width=maxrad-minrad)!=0) {
            l = random();
            radius = width * (l/(double)RAND_MAX) + minrad;
        }

        b.push_back(ball(pos_x, pos_y, pos_z, vel_x, vel_y, vel_z,radius));
    }

    void del_random_ball()
    {
        if(b.size()>0) {
            struct timeval stamp;
            unsigned i;
            
            gettimeofday(&stamp,NULL);
            srandom((unsigned int)stamp.tv_usec);
            i = random() % b.size();
            unsigned end=b.size();

            for(i++; i<end; i++)
                b[i-1]=b[i];

            b.resize(end-1);
        }
    }

    void clear_balls()
    {
        b.clear();
    }

    void clear()
    {
        p.clear();
        clear_balls();
    }

    int num_planes()
    {
        return p.size();
    }

    int num_balls()
    {
        return b.size();
    }

    point3<float> get_ball_pos(int index)
    {
        assert(index>=0 && index<(int)b.size());
        return b[index].pos;
    }

    float get_ball_radius(int index)
    {
        assert(index>=0 && index<(int)b.size());
        return b[index].radius;
    }


    private:
    //! checks and marks balls being in the outer space
    void check(ball& bl)
    {
        float dist;
        unsigned minpi;
        float mindist=1;
        unsigned pend=p.size();

        for(unsigned pi=0; pi<pend; pi++) {
            plane& pl = p[pi];
            dist = pl.A*bl.pos.x + pl.B*bl.pos.y + pl.C*bl.pos.z + pl.D;
            if(dist<mindist) {
                mindist = dist;
                minpi = pi;
            }
        }

        if(mindist<=0) {
            bl.out=true;
            bl.plane=minpi;
        }
    }

    //! checks if the ball has passed the unpassable borders
    bool unpassable(ball& bl)
    {
        if(bl.pos.x < minx || maxx < bl.pos.x ||
           bl.pos.y < miny || maxy < bl.pos.y ||
           bl.pos.z < minz || maxz < bl.pos.z)
            return true;
        else
            return false;
    }


    private:
    //! planes
    std::vector<plane> p;
    //! balls
    std::vector<ball> b;
    float minx, maxx;   // if it happens that a ball run from the cage,
    float miny, maxy;   // it will be deleted after passing any of these
    float minz, maxz;   // borders
};

#endif // LOD_BOX_H
